{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Automatic door control and motion logger\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Aim\n",
    "\n",
    "* This application notebook uses Grove servo and Grove PIR motion sensor to create an Automatic door control system. When the PIR sensor detects a movement the servo will open the door. In the second part of the notebook we analyse the data collected from motion sensor and plot a 24/7 time wheel which depicts a heatmap showing the busy hours of people entering through the automatic door.\n",
    "\n",
    "## References\n",
    "* [Jupyterplot](https://lvwerra.github.io/jupyterplot/)\n",
    "* [Grove PIR](https://www.seeedstudio.com/Grove-PIR-Motion-Sensor.html)   \n",
    "* [Grove Servo](https://wiki.seeedstudio.com/Grove-Servo.html)\n",
    "* [Grove I2C OLED](https://wiki.seeedstudio.com/Grove-OLED_Display_0.96inch/)\n",
    "* [Grove Base Shield V2.0](https://www.seeedstudio.com/Base-Shield-V2.html)   \n",
    "* [time-wheel-in-python3-pandas](https://stackoverflow.com/questions/40352607/time-wheel-in-python3-pandas/49010015)\n",
    "\n",
    "## Last revised\n",
    "* 20 April 2021\n",
    "    + Initial version\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Import libraries and load _base_ Overlay"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datetime\n",
    "import random\n",
    "from collections import OrderedDict\n",
    "from time import sleep\n",
    "\n",
    "import pandas as pd\n",
    "import pytz\n",
    "from pynq.overlays.base import BaseOverlay\n",
    "from pynq_peripherals import ArduinoSEEEDGroveAdapter\n",
    "from pytz import timezone\n",
    "\n",
    "base = BaseOverlay('base.bit')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-info\">\n",
    "   <h4 class=\"alert-heading\">Library Dependencies </h4>\n",
    "Before proceeding the following libraries must be installed\n",
    "    <ul>\n",
    "        <li> Jupyterplot </li>\n",
    "    </ul>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "try:\n",
    "    from jupyterplot import ProgressPlot\n",
    "except ImportError:\n",
    "    print(\"This script requires the jupyterplot module \\nInstall with: \"\n",
    "          \"sudo pip3 install jupyterplot\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Constructing application with Grove Base Shield V2.0 (Arduino)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-warning\">    \n",
    "   <h4 class=\"alert-heading\">Make Physical Connections </h4>\n",
    "    <ul>\n",
    "        <li>Insert the Grove Base Shield into the Arduino connector on the board.</li>\n",
    "        <li>Connect the grove_servo module to D5 connector of the Grove Base Shield.</li>\n",
    "        <li>Connect the grove_pir module to D6 connector of the Grove Base Shield.</li>\n",
    "        <li> Connect the grove_oled module to I2C connector of the Grove Base Shield.</li></ul>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](images/door_control.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Adapter configuration"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "adapter = ArduinoSEEEDGroveAdapter(base.ARDUINO, D6='grove_pir', D5='grove_servo', I2C='grove_oled')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define device objects"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "motion_sensor = adapter.D6\n",
    "servo = adapter.D5\n",
    "oled = adapter.I2C"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Construct the automatic door controller"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def open_door():\n",
    "    for x in range(10, 170, 10):\n",
    "        servo.set_angular_position(x)\n",
    "        sleep(0.01)\n",
    "    for x in range(160, 0, -10):\n",
    "        servo.set_angular_position(x)\n",
    "        sleep(0.1)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-info\">\n",
    "   <h4 class=\"alert-heading\">Notes before running the next cell </h4>\n",
    "    <ul>\n",
    "        <li> Toggle Switch SW0 on the PYNQ-Z2 board is used to enable or disable the motion sensor.</li>\n",
    "        <li> Detection data is being collected while running this cell. To exit the cell just Interrupt the kernel with the stop sign on the tool bar </li>\n",
    "    </ul>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# define counter\n",
    "counter = 0\n",
    "\n",
    "oled.set_default_config()\n",
    "oled.clear_display()\n",
    "detect_time = list()\n",
    "capture = list()\n",
    "\n",
    "pp = ProgressPlot(plot_names=['Detection frequency'],\n",
    "                  line_names=['Door opened'], line_colors=None,\n",
    "                  x_lim=[None, None], y_lim=[0, 1],\n",
    "                  x_label='detection attempts', x_iterator=True, height=None,\n",
    "                  width=600);\n",
    "\n",
    "try:\n",
    "    for i in range(1000):\n",
    "        oled.set_position(0, 0)\n",
    "        oled.put_string('The person')\n",
    "        oled.set_position(2, 0)\n",
    "        oled.put_string('number is:')\n",
    "        oled.set_position(4, 0)\n",
    "        if base.switches[0].read():\n",
    "            if motion_sensor.motion_detected():\n",
    "                detect_time.append(\n",
    "                    datetime.datetime.now(tz=pytz.timezone('US/Pacific')))\n",
    "                capture.append(1)\n",
    "                counter += 1\n",
    "                oled.put_string(str(counter))\n",
    "                d = {'detect_time': detect_time, 'capture': capture}\n",
    "                df = pd.DataFrame(data=d)\n",
    "                pp.update(1)\n",
    "                pp.update(0)\n",
    "                open_door()\n",
    "                sleep(1)\n",
    "            else:\n",
    "                oled.put_string(str(counter))\n",
    "                detect_time.append(\n",
    "                    datetime.datetime.now(tz=pytz.timezone('US/Pacific')))\n",
    "                capture.append(0)\n",
    "                pp.update(0)\n",
    "                pp.update(0)\n",
    "        sleep(0.1)\n",
    "except KeyboardInterrupt:\n",
    "    print('Stopping the loop')\n",
    "    \n",
    "pp.finalize()\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Sample the collected data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Logging movements and weekly table"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# generate the table with timestamps\n",
    "times = df.detect_time.where(df.capture == 1).dropna()\n",
    "times.name = None\n",
    "data = pd.crosstab(times.dt.strftime(\"%A\"), times.dt.hour.apply(\n",
    "    lambda x: '{:02d}:00'.format(x))).fillna(0)\n",
    "\n",
    "test = [str(i) + ':00' if i > 9 else '0' + str(i) + ':00' for i in range(24)]\n",
    "\n",
    "# initialize data of lists.\n",
    "days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday']\n",
    "dlist = OrderedDict(dict(zip(days, [([0] * 24) for _ in range(7)])))\n",
    "\n",
    "# Create DataFrame\n",
    "df_master = pd.DataFrame(dlist, index=test)\n",
    "\n",
    "df_weekly = df_master.T\n",
    "for i in test:\n",
    "    for j in dlist.keys():\n",
    "        exists = i in data\n",
    "        if exists:\n",
    "            exists1 = j in data[i]\n",
    "            if exists1:\n",
    "                df_weekly[i].loc[j] = data[i].loc[j]\n",
    "\n",
    "print(df_weekly.T)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "<div class=\"alert alert-box alert-info\">\n",
    "   <p class=\"alert-heading\">Note: Below we show simulated table for one full week of detections for demonstration purpose </p>\n",
    "</div>"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "test_sim = [str(i) + ':00' if i > 9 else '0' + str(i) + ':00' for i in range(24)]\n",
    "\n",
    "# initialize data of lists.\n",
    "dlist_sim = OrderedDict(dict(zip(days, [random.sample(range(0, 50), 24) for _ in range(7)])))\n",
    "\n",
    "# Create DataFrame\n",
    "df_master = pd.DataFrame(dlist_sim, index=test_sim)\n",
    "\n",
    "df_weekly_sim = df_master.T\n",
    "\n",
    "print(df_weekly_sim.T)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright (C) 2021 Xilinx, Inc\n",
    "\n",
    "SPDX-License-Identifier: BSD-3-Clause"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "----"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
